package core.validate;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public abstract class Rule <T extends Rule<T>> implements Cloneable{
    public final String key;
    public final List<RuleAction> actions = new ArrayList<>();
    private final Set<String> aliasNames = new HashSet<>();

    protected String defaultMessage = "数据错误";
    protected abstract T inst();
    private final T self = inst();

    private RulePreCheck preCheckAction = null;

    private boolean optional = true;

    public Rule(String key){
        this.key = key;
    }

    public String getMessage(){
        return defaultMessage;
    }

    public String getMessage(String message){
        return message == null ? defaultMessage : message;
    }

    public T setMessage(String message){
        defaultMessage = message;
        return self;
    }

    public Set<String> getAlias(){
        return aliasNames;
    }

    public T setAlias(String[] keys){
        Collections.addAll(aliasNames, keys);
        return self;
    }

    public T setAlias(String key){
        aliasNames.add(key);
        return self;
    }

    public boolean hasPreCheck(){
        return preCheckAction != null;
    }

    public T onPreCheck(RulePreCheck action){
        preCheckAction = action;
        return self;
    }

    public Object doPreCheck(Object value){
        if(preCheckAction != null){
            return preCheckAction.apply(value);
        }
        return value;
    }

    public boolean isRequire(){
        return !optional;
    }

    /**************************** Rule ****************************/

    public T test(RuleTest action, String message){
        actions.add(holder -> {
            holder.setStatus(action.apply(holder)).setMessage(getMessage(message));
        });
        return self;
    }

    public T regex(String pattern, String message){
        actions.add(holder -> {
            boolean status = false;
            String value = null;
            if(holder.getValue() instanceof String){
                value = holder.getValue();
            }else{
                try{
                    value = holder.getValue().toString();
                }catch (Exception ignored){}
            }
            if(value != null) {
                // holder.setStatus(action.apply(holder)).setMessage(message);
                Matcher matcher = Pattern.compile(pattern).matcher(value);
                status = matcher.matches();
            }
            holder.setStatus(status).setMessage(getMessage(message));
        });
        return self;
    }

    public T notRegex(String pattern, String message){
        actions.add(holder -> {
            boolean status = false;
            String value = null;
            if(holder.getValue() instanceof String){
                value = holder.getValue();
            }else{
                try{
                    value = holder.getValue().toString();
                }catch (Exception ignored){}
            }
            if(value != null) {
                // holder.setStatus(action.apply(holder)).setMessage(message);
                Matcher matcher = Pattern.compile(pattern).matcher(value);
                status = matcher.matches();
            }
            holder.setStatus(!status).setMessage(getMessage(message));
        });
        return self;
    }
    @SuppressWarnings("unchecked")
    public T require(String message){
        if(optional) {
            optional = false;
            actions.add(0, holder -> {
                boolean status = true;
                Object value = holder.getValue();
                if(value == null){
                    status = false;
                }else{
                    if(value instanceof String strValue){
                        status = !strValue.isEmpty();
                    }else if(value instanceof List){
                        List<Object> arrValue = (List<Object>) value;
                        status = !arrValue.isEmpty();
                    }else if(value instanceof Map){
                        Map<String, Object> objValue = (Map<String, Object>) value;
                        status = !objValue.isEmpty();
                    }
                }
                holder.setStatus(status).setMessage(getMessage(message));
            });
        }
        return self;
    }

    public T in(Object[] target, String message){
        actions.add(holder -> {
            boolean status = Arrays.asList(target).contains(holder.getValue());
            holder.setStatus(status).setMessage(getMessage(message));
        });
        return self;
    }

    public T notIn(Object[] target, String message){
        actions.add(holder -> {
            boolean status = Arrays.asList(target).contains(holder.getValue());
            holder.setStatus(!status).setMessage(getMessage(message));
        });
        return self;
    }

    /*************************** Static ***************************/
    public static ArrayRule Array(String name) {
        return new ArrayRule(name);
    }

    public static BooleanRule Boolean(String name) {
        return new BooleanRule(name);
    }

    public static DateRule Date(String name) {
        return new DateRule(name);
    }

    public static FileRule File(String name) {
        return new FileRule(name);
    }

    public static NumberRule Number(String name) {
        return new NumberRule(name);
    }

    public static StringRule String(String name) {
        return new StringRule(name);
    }

    public static List<ArrayRule> ArrayGroup(String[] keys, RuleGroup<ArrayRule> action){
        List<ArrayRule> group = new ArrayList<>();
        for(String key: keys){
            ArrayRule item = new ArrayRule(key);
            action.apply(item);
            group.add(item);
        }
        return group;
    }
    public static <T> List<ArrayRule> ArrayGroup(Map<String, T> keys, RuleGroupWith<ArrayRule, T> action){
        List<ArrayRule> group = new ArrayList<>();
        for(Map.Entry<String, T> entry: keys.entrySet()){
            ArrayRule item = new ArrayRule(entry.getKey());
            action.apply(item, entry.getValue());
            group.add(item);
        }
        return group;
    }

    public static List<BooleanRule> BooleanGroup(String[] keys, RuleGroup<BooleanRule> action){
        List<BooleanRule> group = new ArrayList<>();
        for(String key: keys){
            BooleanRule item = new BooleanRule(key);
            action.apply(item);
            group.add(item);
        }
        return group;
    }
    public static <T> List<BooleanRule> BooleanGroup(Map<String, T> keys, RuleGroupWith<BooleanRule, T> action){
        List<BooleanRule> group = new ArrayList<>();
        for(Map.Entry<String, T> entry: keys.entrySet()){
            BooleanRule item = new BooleanRule(entry.getKey());
            action.apply(item, entry.getValue());
            group.add(item);
        }
        return group;
    }

    public static List<FileRule> FileGroup(String[] keys, RuleGroup<FileRule> action){
        List<FileRule> group = new ArrayList<>();
        for(String key: keys){
            FileRule item = new FileRule(key);
            action.apply(item);
            group.add(item);
        }
        return group;
    }
    public static <T> List<FileRule> FileGroup(Map<String, T> keys, RuleGroupWith<FileRule, T> action){
        List<FileRule> group = new ArrayList<>();
        for(Map.Entry<String, T> entry: keys.entrySet()){
            FileRule item = new FileRule(entry.getKey());
            action.apply(item, entry.getValue());
            group.add(item);
        }
        return group;
    }

    public static List<DateRule> DateGroup(String[] keys, RuleGroup<DateRule> action){
        List<DateRule> group = new ArrayList<>();
        for(String key: keys){
            DateRule item = new DateRule(key);
            action.apply(item);
            group.add(item);
        }
        return group;
    }
    public static <T> List<DateRule> DateGroup(Map<String, T> keys, RuleGroupWith<DateRule, T> action){
        List<DateRule> group = new ArrayList<>();
        for(Map.Entry<String, T> entry: keys.entrySet()){
            DateRule item = new DateRule(entry.getKey());
            action.apply(item, entry.getValue());
            group.add(item);
        }
        return group;
    }

    public static List<NumberRule> NumberGroup(String[] keys, RuleGroup<NumberRule> action){
        List<NumberRule> group = new ArrayList<>();
        for(String key: keys){
            NumberRule item = new NumberRule(key);
            action.apply(item);
            group.add(item);
        }
        return group;
    }
    public static <T> List<NumberRule> NumberGroup(Map<String, T> keys, RuleGroupWith<NumberRule, T> action){
        List<NumberRule> group = new ArrayList<>();
        for(Map.Entry<String, T> entry: keys.entrySet()){
            NumberRule item = new NumberRule(entry.getKey());
            action.apply(item, entry.getValue());
            group.add(item);
        }
        return group;
    }

    public static List<StringRule> StringGroup(String[] keys, RuleGroup<StringRule> action){
        List<StringRule> group = new ArrayList<>();
        for(String key: keys){
            StringRule item = new StringRule(key);
            action.apply(item);
            group.add(item);
        }
        return group;
    }
    public static <T> List<StringRule> StringGroup(Map<String, T> keys, RuleGroupWith<StringRule, T> action){
        List<StringRule> group = new ArrayList<>();
        for(Map.Entry<String, T> entry: keys.entrySet()){
            StringRule item = new StringRule(entry.getKey());
            action.apply(item, entry.getValue());
            group.add(item);
        }
        return group;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

